﻿using System;
using Bookvar.ObjectModel.Actions;
using Bookvar.ServiceModel;
using Bookvar.UI;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;

namespace Bookvar.UnitTests.ServiceModel
{
    /// <summary>
    /// Tests for the <see cref="ActionService"/> class.
    /// </summary>
    [TestClass]
    public class ActionServiceTests
    {
        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext { get; set; }

        [TestMethod]
        public void CanCreateNew()
        {
            IActionService actionService = new ActionService();

            Assert.IsNotNull( actionService );
        }

        [TestMethod]
        public void SupportsUndo()
        {
            IActionService actionService = new ActionService();

            Assert.IsTrue( actionService.CanUndo );
        }

        [TestMethod]
        public void SupportsRedo()
        {
            IActionService actionService = new ActionService();

            Assert.IsTrue( actionService.CanRedo );
        }

        [TestMethod]
        [ExpectedException( typeof ( ArgumentNullException ) )]
        public void Execute_WithNullAction_Throws()
        {
            IActionService actionService = new ActionService();

            actionService.Execute( null );
        }

        [TestMethod]
        public void Execute_ExecutesTheAction()
        {
            var action = MockRepository.GenerateStub<IAction>();

            IActionService actionService = new ActionService();
            actionService.Execute( action );

            action.AssertWasCalled( x => x.Execute() );
        }

        [TestMethod]
        public void HasActionToUndo_WithNoExecutedAction_IsFalse()
        {
            IActionService actionService = new ActionService();

            Assert.IsFalse( actionService.HasActionToUndo );
        }

        [TestMethod]
        public void HasActionToUndo_AfterAnActionIsExecuted_IsTrue()
        {
            IActionService actionService = new ActionService();
            var action = MockRepository.GenerateStub<IAction>();
            action.Stub( x => action.CanUndo ).Return( true ).Repeat.Any();

            actionService.Execute( action );
            Assert.IsTrue( actionService.HasActionToUndo );
        }

        [TestMethod]
        public void Undo_WithNoActionExecuted_DoesNotThrow()
        {
            IActionService actionService = new ActionService();

            actionService.Undo();
        }

        [TestMethod]
        public void Undo_AfterAnActionIsExecuted_CallsUndoOnTheAction()
        {
            IActionService actionService = new ActionService();
            var action = MockRepository.GenerateStub<IAction>();
            action.Stub( x => x.CanUndo ).Return( true ).Repeat.Any();

            actionService.Execute( action );
            actionService.Undo();

            // TODO: assert it was called once. Can't find a way to do it with the 3.5beta version of Rhino.Mocks.
            action.AssertWasCalled( x => x.Undo() );
        }

        [TestMethod]
        public void Undo_AfterAnActionIsExecuted_MovesTheActionForRedo()
        {
            var action = MockRepository.GenerateStub<IAction>();
            action.Stub( x => x.CanUndo ).Return( true ).Repeat.Any();
            action.Stub( x => x.CanRedo ).Return( true ).Repeat.Any();

            IActionService actionService = new ActionService();
            actionService.Execute( action );
            actionService.Undo();

            // test if HasActionToRedo is true, because we do not have access to the 
            // internal collections.
            Assert.IsTrue( actionService.HasActionToRedo );
            Assert.IsFalse( actionService.HasActionToUndo );
        }

        [TestMethod]
        public void Undo_AfterMoreThanOneActionIsExecuted_CallsUndoOnTheLastExecutedAction()
        {
            var action1 = MockRepository.GenerateStub<IAction>();
            action1.Stub( x => x.CanUndo ).Return( true ).Repeat.Any();
            var action2 = MockRepository.GenerateStub<IAction>();
            action2.Stub( x => x.CanUndo ).Return( true ).Repeat.Any();

            IActionService actionService = new ActionService();
            actionService.Execute( action1 );
            actionService.Execute( action2 );
            actionService.Undo();

            // TODO: assert it was called once. Can't do it with the 3.5beta version of Rhino.Mocks.
            action2.AssertWasCalled( x => x.Undo() );
        }

        [TestMethod]
        public void HasActionToRedo_WithNoActionUndone_IsFalse()
        {
            IActionService actionService = new ActionService();

            Assert.IsFalse( actionService.HasActionToRedo );
        }

        [TestMethod]
        public void HasActionToRedo_AfterAnActionIsUndone_IsTrue()
        {
            var action = MockRepository.GenerateStub<IAction>();
            action.Stub( x => x.CanUndo ).Return( true ).Repeat.Any();
            action.Stub( x => x.CanRedo ).Return( true ).Repeat.Any();

            IActionService actionService = new ActionService();
            actionService.Execute( action );
            actionService.Undo();

            Assert.IsTrue( actionService.HasActionToRedo );
        }

        [TestMethod]
        public void Redo_WithNoUndoneAction_DoesNotThrow()
        {
            IActionService actionService = new ActionService();

            actionService.Redo();
        }

        // TODO redo with AAA syntax after we can validate a method was called twice.
        [TestMethod]
        public void Redo_AfterActionIsUndone_ExecutesTheActionAgain()
        {
            var repository = new MockRepository();
            var action = repository.StrictMock<IAction>();
            SetupResult.For( action.CanUndo ).Return( true );
            SetupResult.For( action.CanRedo ).Return( true );

            using ( repository.Ordered() )
            {
                action.Execute();
                LastCall.Repeat.Once();
                action.Undo();
                LastCall.Repeat.Once();
                action.Execute();
                LastCall.Repeat.Once();
            }

            repository.ReplayAll();

            IActionService actionService = new ActionService();
            actionService.Execute( action );
            actionService.Undo();
            actionService.Redo();

            repository.VerifyAll();
        }

        [TestMethod]
        public void Redo_OfUndoneAction_MovesTheActionForUndo()
        {
            var action = MockRepository.GenerateStub<IAction>();
            action.Stub( x => x.CanUndo ).Return( true ).Repeat.Any();
            action.Stub( x => x.CanRedo ).Return( true ).Repeat.Any();

            IActionService actionService = new ActionService();
            actionService.Execute( action );
            actionService.Undo();
            actionService.Redo();

            Assert.IsTrue( actionService.HasActionToUndo );
            Assert.IsFalse( actionService.HasActionToRedo );
        }

        [TestMethod]
        public void Redo_AfterTwoOrMoreActionsAreUndone_ExecutesTheLastUndoneAction()
        {
            var action1 = MockRepository.GenerateStub<IAction>();
            var action2 = MockRepository.GenerateStub<IAction>();
            action1.Stub( x => x.CanUndo ).Return( true ).Repeat.Any();
            action2.Stub( x => x.CanUndo ).Return( true ).Repeat.Any();
            action1.Stub( x => x.CanRedo ).Return( true ).Repeat.Any();
            action2.Stub( x => x.CanRedo ).Return( true ).Repeat.Any();

            IActionService actionService = new ActionService();
            actionService.Execute( action1 );
            actionService.Execute( action2 );
            actionService.Undo(); // undo action2
            actionService.Undo(); // undo action1
            actionService.Redo(); // redo action1

            action1.AssertWasCalled( x => x.Execute() );
            Assert.AreEqual( 2, action1.GetArgumentsForCallsMadeOn( x => x.Execute() ).Count );

            action2.AssertWasCalled( x => x.Execute() );
            Assert.AreEqual( 1, action2.GetArgumentsForCallsMadeOn( x => x.Execute() ).Count );

        }

        [TestMethod]
        public void Execute_AnActionThatCannotBeUndone_TheActionIsNotSavedForUndo()
        {
            var action = MockRepository.GenerateStub<IAction>();
            action.Stub( x => x.CanUndo ).Return( false ).Repeat.Any();

            IActionService actionService = new ActionService();
            actionService.Execute( action );

            Assert.IsFalse( actionService.HasActionToUndo );
        }

        [TestMethod]
        public void Execute_AnActionThatCannotBeUndone_TheActionIsDisposed()
        {
            var action = MockRepository.GenerateStub<IAction>();
            action.Stub( x => x.CanUndo ).Return( false ).Repeat.Any();

            IActionService actionService = new ActionService();
            actionService.Execute( action );

            action.AssertWasCalled( x => x.Dispose() );
        }

        [TestMethod]
        public void Undo_AnActionThatCannotBeRedone_TheActionIsNotSavedForRedo()
        {
            var action = MockRepository.GenerateStub<IAction>();
            action.Stub( x => x.CanUndo ).Return( true ).Repeat.Any();
            action.Stub( x => x.CanRedo ).Return( false ).Repeat.Any();

            IActionService actionService = new ActionService();
            actionService.Execute( action );
            actionService.Undo();

            Assert.IsFalse( actionService.HasActionToRedo );
        }

        [TestMethod]
        public void Undo_AnActionThatCannotBeRedone_TheActionIsDisposed()
        {
            var action = MockRepository.GenerateStub<IAction>();
            action.Stub( x => x.CanUndo ).Return( true ).Repeat.Any();
            action.Stub( x => x.CanRedo ).Return( false ).Repeat.Any();

            IActionService actionService = new ActionService();
            actionService.Execute( action );
            actionService.Undo();

            action.AssertWasCalled( x => x.Dispose() );
        }

        [TestMethod]
        public void ExecuteExecuteUndoExecute_ClearsTheActionForRedoAndDisposeIt()
        {
            var action1 = MockRepository.GenerateStub<IAction>();
            var action2 = MockRepository.GenerateStub<IAction>();
            var action3 = MockRepository.GenerateStub<IAction>();
            action1.Stub( x => x.CanUndo ).Return( true ).Repeat.Any();
            action2.Stub( x => x.CanUndo ).Return( true ).Repeat.Any();
            action3.Stub( x => x.CanUndo ).Return( true ).Repeat.Any();
            action1.Stub( x => x.CanRedo ).Return( true ).Repeat.Any();
            action2.Stub( x => x.CanRedo ).Return( true ).Repeat.Any();
            action3.Stub( x => x.CanRedo ).Return( true ).Repeat.Any();

            IActionService actionService = new ActionService();
            actionService.Execute( action1 );
            actionService.Execute( action2 );
            actionService.Undo(); // undo action2
            actionService.Execute( action3 );

            Assert.IsFalse( actionService.HasActionToRedo );
            action2.AssertWasCalled( x => x.Dispose() );
        }
    }
}